//
// CATGL - Cheap Action-game Tool with WebGL.
//
// Copyright 2010, tmasda.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

function CameraSystem() {}

CameraSystem.prototype = {
    // 初期化
    initialize: function(w,h,fovy,aspect,znear,zfar) {
        // 指定しなかった場合にはデフォルトの値を使う
        var default_val = {
            "fovy"   : 45.0,
            "aspect" : 1.0,
            "znear"  : 0.1,
            "zfar"   : 100,
        };
        default_val.width = w;
        default_val.height = h;
        if (fovy){
            default_val.fovy = fovy;
        }
        if (aspect){
            default_val.aspect = aspect;
        }
        if (znear){
            default_val.znear = znear;
        }
        if (zfar){
            default_val.zfar = zfar;
        }
        this.screen = default_val;
        return this;
    },
    // カメラ用の行列の作成
    toMatrix: function(dest) {
        //var zvec = this.eye_rvect.toUnitVector(); 
        //var up = $V([0,1,0]);
        //var xvec = up.cross(zvec).toUnitVector();
        //var yvec = zvec.cross(xvec).toUnitVector();

        //var m = $M([[xvec.e(1),xvec.e(2),xvec.e(3),0],
        //            [yvec.e(1),yvec.e(2),yvec.e(3),0],
        //            [zvec.e(1),zvec.e(2),zvec.e(3),0],
        //            [0, 0, 0, 1]]);
        //var pos = this.eye_pos;
        //var t = $M([[1, 0, 0, -pos.e(1)],
        //            [0, 1, 0, -pos.e(2)],
        //            [0, 0, 1, -pos.e(3)],
        //            [0, 0, 0, 1]]);
        //return m.x(t);
        if (!dest){
            dest = mat4.create();
        }
        var zvec = new Float32Array(3), 
            yvec = new Float32Array(3),
            xvec = new Float32Array(3), 
            up = new Float32Array(3);
        var eyex = this.eye_pos[0];
        var eyey = this.eye_pos[1];
        var eyez = this.eye_pos[2];

        vec3.normalize(this.eye_rvect, zvec);
        up = vec3.create([0, 1, 0]); 
        vec3.cross(up, zvec, xvec);
        vec3.normalize(xvec, xvec);
        vec3.cross(zvec, xvec, yvec);
        vec3.normalize(yvec, yvec);
        dest[0] = xvec[0];
        dest[1] = yvec[0];
        dest[2] = zvec[0];
        dest[3] = 0;
        dest[4] = xvec[1];
        dest[5] = yvec[1];
        dest[6] = zvec[1];
        dest[7] = 0;
        dest[8] = xvec[2];
        dest[9] = yvec[2];
        dest[10] = zvec[2];
        dest[11] = 0;
        dest[12] = -(xvec[0]*eyex + xvec[1]*eyey + xvec[2]*eyez);
        dest[13] = -(yvec[0]*eyex + yvec[1]*eyey + yvec[2]*eyez);
        dest[14] = -(zvec[0]*eyex + zvec[1]*eyey + zvec[2]*eyez);
        dest[15] = 1;

        return dest;
    },
    // カメラ用の行列の作成(3x3)
    toMatrix3x3: function(dest) {
        //var zvec = this.eye_rvect.toUnitVector(); 
        //var up = $V([0,1,0]);
        //var xvec = up.cross(zvec).toUnitVector();
        //var yvec = zvec.cross(xvec).toUnitVector();

        //return $M([[xvec.e(1),xvec.e(2),xvec.e(3)],
        //            [yvec.e(1),yvec.e(2),yvec.e(3)],
        //            [zvec.e(1),zvec.e(2),zvec.e(3)]]);
        if (!dest){
            dest = mat3.create();
        }
        var zvec = new Float32Array(3), 
            yvec = new Float32Array(3),
            xvec = new Float32Array(3), 
            up = new Float32Array(3);
        var eyex = this.eye_pos[0];
        var eyey = this.eye_pos[1];
        var eyez = this.eye_pos[2];

        vec3.normalize(this.eye_rvect, zvec);
        up = vec3.create([0, 1, 0]); 
        vec3.cross(up, zvec, xvec);
        vec3.normalize(xvec, xvec);
        vec3.cross(zvec, xvec, yvec);
        vec3.normalize(yvec, yvec);
        dest[0] = xvec[0];
        dest[1] = yvec[0];
        dest[2] = zvec[0];
        dest[3] = xvec[1];
        dest[4] = yvec[1];
        dest[5] = zvec[1];
        dest[6] = xvec[2];
        dest[7] = yvec[2];
        dest[8] = zvec[2];
        return dest;
    },
    // WebGLへの描画前の設定とカメラ座標への変換行列の取得
    exec: function() {
        var screen = this.screen;
        
        catGL.gl.viewport(0, 0, screen.width, screen.height);
        //this.pMatrix = makePerspective(screen.fovy, screen.aspect, screen.znear, screen.zfar);
        //this.pMatrixFlatten = new Float32Array(this.pMatrix.flatten());
        this.pMatrix = mat4.perspective(screen.fovy, screen.aspect, screen.znear, screen.zfar);
        // カメラのアニメーションが登録されていれば実行する
        if (this.animation_que.length){
            // 戻り値が0ならキューから削除する
            if (!this.animation_que[0]()){
                this.animation_que.shift();
            }
        }
        // カメラの座標系へ変換するための行列の作成
        return this.toMatrix(this.wcMatrix);
    },
    // カメラ座標の取得
    getPos: function() {
        //return this.eye_pos.dup();
        var v = vec3.create();
        vec3.set(this.eye_pos, v);
        return v;
    },
    // カメラの座標の設定
    setPos: function(px, py, pz) {
        //this.eye_pos = $V([px, py, pz]);
        this.eye_pos[0] = px;
        this.eye_pos[1] = py;
        this.eye_pos[2] = pz;
    },
    // x軸でのカメラの回転
    pitch: function(angle) {
        //var matrix = Matrix.RotationX(angle).ensure4x4().x(this.toMatrix());
        //this.eye_rvect.elements[0] = matrix.elements[2][0];
        //this.eye_rvect.elements[1] = matrix.elements[2][1];
        //this.eye_rvect.elements[2] = matrix.elements[2][2];
        var matrix = mat4.create();
        mat4.identity(matrix);
        mat4.rotateX(matrix, angle);
        var cmatrix = this.toMatrix();
        mat4.multiply(matrix, cmatrix);
        //mat4.multiplyVec3(matrix, this.eye_rvect, this.eye_rvect)
        //vec3.set([matrix[8], matrix[9], matrix[10]], );
        this.eye_rvect[0] = matrix[2];
        this.eye_rvect[1] = matrix[6];
        this.eye_rvect[2] = matrix[10];
    },
    // y軸でのカメラの回転
    yaw: function(angle) {
        //this.eye_rvect = Matrix.RotationY(angle).x(this.eye_rvect);
        var matrix = mat4.create();
        mat4.identity(matrix);
        mat4.rotateY(matrix, angle);
        mat4.multiplyVec3(matrix, this.eye_rvect)

    },
    // lookat
    lookAt: function(ox, oy, oz) {
        //var epos = this.eye_pos;
        //this.eye_rvect = $V([epos.e(1)-ox, epos.e(2)-oy, epos.e(3)-oz]).toUnitVector();
        vec3.subtract(this.eye_pos, [ox, oy, oz], this.eye_rvect);
        vec3.normalize(this.eye_rvect);
    },
    // lookat vec3で指定
    lookAtVec3: function(pos) {
        //var epos = this.eye_pos;
        //this.eye_rvect = $V([epos.e(1)-ox, epos.e(2)-oy, epos.e(3)-oz]).toUnitVector();
        vec3.subtract(this.eye_pos, pos, this.eye_rvect);
        vec3.normalize(this.eye_rvect);
    },
    // カメラの移動
    move: function(mx, my, mz) {
        var matrix = this.toMatrix3x3();
        //var v = matrix.transpose().x($V([mx, my, mz]));
        //this.eye_pos = this.eye_pos.add(v);
        var v = new Float32Array(3);
        mat3.transpose(matrix, matrix); 
        mat3.multiplyVec3(matrix, new Float32Array([mx, my, mz]), v);
        vec3.add(this.eye_pos, v, this.eye_pos);
    },
    // hermite補完アニメーションの登録
    // sposとsvecが指定されない場合には現在のカメラからスタート      
    addHermiteAnimation: function(time,spos, srvec, endpos, endrvec) { 
        this.animation_que.push(hermiteAnime(this, time, spos, srvec, endpos, endrvec));
    },
    // debug  カメラの座標と視線の逆ベクトルの文字列への変換
    getCameraString: function() {
        return "camera position:" + "[" + this.eye_pos[0].toFixed(4) + "," + 
                                          this.eye_pos[1].toFixed(4) + "," + 
                                          this.eye_pos[2].toFixed(4) + "]<br>" +
                "camera rvect   :" + "[" + this.eye_rvect[0].toFixed(4) + "," + 
                                           this.eye_rvect[1].toFixed(4) + "," + 
                                           this.eye_rvect[2].toFixed(4) + "]<br>";
        },
    // debug　カメラ行列を文字列へ変換
    getMatrixString: function() {
        //return mht(this.toMatrix());
        return mat4.str(this.toMatrix());
    },
    // debug　カメラ行列の逆行列を文字列へ変換
    //getInvMatrixString: function() {
    //    return mht(this.toMatrix().inverse());
    //},
};

// カメラオブジェクトの生成
CameraSystem.create = function() {
    var camera = new CameraSystem();
    camera.eye_pos = new Float32Array([0, 0, 0]);
    camera.eye_rvect = new Float32Array([0, 0, 1]);
    camera.pMatrix = mat4.create();    // perspective変換用
    camera.wcMatrix = mat4.create();   // word座標系からcamera座標系への変換
    camera.animation_que = []; // カメラアニメーションのキュー
    return camera;
}

// Hermiteカメラアニメーション関数の生成
var hermiteAnime = function(camera, time, spos, srvec, epos, ervec){
    var elapse;
    var lastTime = 0; 
    return function(){
        var t;
        var rt;
        var x, y, z;
        var ret = 1;
        if (lastTime == 0){
            elapse = 0; 
            lastTime = new Date().getTime();
            spos = spos || camera.eye_pos;
            srvec = srvec || camera.eye_rvect;
        } else {
            var curTime = new Date().getTime();
            elapse = curTime - lastTime;
            curTime = lastTime;
        }
        t = elapse / time;
        if (t >= 1){
            t = 1;
            ret = 0;
        }
        //camera.eye_pos = $V(hermite(t, spos, srvec, epos, ervec));
        vec3.set(hermite(t, spos, srvec, epos, ervec), camera.eye_pos);
        rt = 1 - t;
        x = srvec[0]*rt + ervec[0]*t;
        y = srvec[1]*rt + ervec[1]*t;
        z = srvec[2]*rt + ervec[2]*t;
        //camera.eye_rvect = $V([x, y, z]).toUnitVector();
        vec3.normalize([x, y, z], camera.eye_rvect);
        
        return ret;
    };
}

